package com.horizon.common.util;

import com.alibaba.fastjson.JSONObject;
import com.horizon.common.entity.Token;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.time.Instant;
import java.util.Base64;

/**
 * 加解密工具类
 *
 * @author wangfuwei
 * @version 2018-07-13
 */
@Slf4j
public class SecrecyUtil {

    /**
     * 生成token(已加密处理)
     *
     * @param token token对象
     */
    public static String generateToken(Token token) {
        return generateToken(token, Token.SECRET_KEY);
    }

    /**
     * 生成token(已加密处理)
     *
     * @param token     token对象
     * @param secretKey sign比对私钥字符串
     */
    public static String generateToken(Token token, String secretKey) {
        try {
            Base64.Encoder encoder = Base64.getEncoder();
            String header = encoder.encodeToString("{\"alg\" : \"HS256\"}".getBytes());

            String payload = JSONObject.toJSONString(token);
            payload = encoder.encodeToString(payload.getBytes());

            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
            mac.init(keySpec);
            String sign = encoder.encodeToString(mac.doFinal(payload.getBytes()));

            return encryptByAES(header + "." + payload + "." + sign, secretKey);
        } catch (Exception e) {
            log.error("generateToken is error", e);
        }
        return null;
    }

    /**
     * 解析token
     *
     * @param tokenStr token字符串
     */
    public static Token parseToken(String tokenStr) {
        return parseToken(tokenStr, Token.SECRET_KEY);
    }

    /**
     * 解析token
     *
     * @param tokenStr  token字符串
     * @param secretKey sign比对私钥字符串
     */
    public static Token parseToken(String tokenStr, String secretKey) {
        try {
            if (tokenStr == null) return null;
            tokenStr = decryptByAES(tokenStr, secretKey);
            if (tokenStr.split("\\.").length == 3) {
                String payload = new String(Base64.getDecoder().decode(tokenStr.split("\\.")[1].getBytes()));//payload
                String sign = tokenStr.split("\\.")[2];//token签名

                JSONObject oriToken = (JSONObject) JSONObject.parse(payload);
                Token token = JSONObject.toJavaObject(oriToken, Token.class);

                Method method = Token.class.getDeclaredMethod("setCreateTime", Instant.class);
                method.setAccessible(true);
                method.invoke(token, Instant.ofEpochMilli(oriToken.getLongValue("createTime")));

                method = Token.class.getDeclaredMethod("setExpiryTime", Instant.class);
                method.setAccessible(true);
                method.invoke(token, Instant.ofEpochMilli(oriToken.getLongValue("expiryTime")));

                String newSign = decryptByAES(generateToken(token, secretKey), secretKey).split("\\.")[2];//新签名
                if (sign.equals(newSign)) {
                    return token;
                }
            }
        } catch (Exception e) {
            log.error("parseToken is error", e);
        }
        return null;
    }

    /**
     * 字符串加密（RSA公钥加密）
     *
     * @param data 加密字符串
     * @param key  公钥
     */
    public static String encryptByRSAPublic(String data, RSAPublicKey key) {
        try {
            //实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            //初始化公钥
            //密钥材料转换
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key.getEncoded());
            //产生公钥
            PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
            //数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
        } catch (Exception e) {
            log.error("encryptByRSAPublic is error", e);
        }
        return null;
    }

    /**
     * 字符串加密（RSA私钥加密）
     *
     * @param data 加密字符串
     * @param key  私钥
     */
    public static String encryptByRSAPrivate(String data, RSAPrivateKey key) {
        try {
            //取得私钥
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            //生成私钥
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
            //数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
        } catch (Exception e) {
            log.error("encryptByRSAPrivate is error", e);
        }
        return null;
    }

    /**
     * 字符串解密（RSA私钥解密）
     *
     * @param data 解密字符串
     * @param key  私钥
     */
    public static String decryptByRSAPrivate(String data, RSAPrivateKey key) {
        try {
            //取得私钥
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            //生成私钥
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
            //数据解密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
        } catch (Exception e) {
            log.error("decryptByRSAPrivate is error", e);
        }
        return null;
    }

    /**
     * 字符串解密（RSA公钥解密）
     *
     * @param data 解密字符串
     * @param key  公钥
     */
    public static String decryptByRSAPublic(String data, RSAPublicKey key) {
        try {
            //实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            //初始化公钥
            //密钥材料转换
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key.getEncoded());
            //产生公钥
            PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
            //数据解密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, pubKey);
            return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
        } catch (Exception e) {
            log.error("decryptByRSAPublic is error", e);
        }
        return null;
    }

    /**
     * 生成RSA密钥对文件
     *
     * @param keySize 密钥对位数
     * @param path    密钥文件存放路径
     */
    public static void generateRSAKey(Integer keySize, String path) {
        try {
            if (keySize == null) keySize = 1024;
            if (keySize % 64 != 0 || keySize < 512 || keySize > 65536) {
                throw new Exception("密钥长度不合法:密钥长度必须是64的倍数，在512到65536位之间");
            }
            if (path == null) {
                path = "";
            }

            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(keySize, new SecureRandom());
            KeyPair kp = kpg.generateKeyPair();
            Key publicKey = kp.getPublic();
            Key privateKey = kp.getPrivate();
            ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream(path + File.separator + "public.keystore"));
            ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream(path + File.separator + "private.keystore"));
            oos1.writeObject(publicKey);
            oos2.writeObject(privateKey);
            oos1.close();
            oos2.close();
        } catch (Exception e) {
            log.error("generateRSAKey is error", e);
        }
    }

    /**
     * 生成RSA密钥对字符串(BigInteger 转为 字符串)
     *
     * @param keySize 密钥对位数
     * @return keys[0][0]:公钥模量 keys[0][1]:公钥指数 keys[1][0]:私钥模量 key[1][1]:私钥指数
     */
    private static String[][] generateRSAKeyString(Integer keySize) {
        try {
            if (keySize == null) keySize = 512;
            if (keySize % 64 != 0 || keySize < 512 || keySize > 65536) {
                throw new Exception("密钥长度不合法:密钥长度必须是64的倍数，在512到65536位之间");
            }

            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(keySize, new SecureRandom());
            KeyPair kp = kpg.generateKeyPair();
            Key publicKey = kp.getPublic();
            Key privateKey = kp.getPrivate();

            String algorithm = publicKey.getAlgorithm();
            KeyFactory keyFact = KeyFactory.getInstance(algorithm);

            RSAPublicKeySpec keySpec = keyFact.getKeySpec(publicKey, RSAPublicKeySpec.class);
            RSAPrivateCrtKeySpec privateKeySpec = keyFact.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);

            String[][] keys = new String[2][2];

            keys[0][0] = keySpec.getModulus().toString();
            keys[0][1] = keySpec.getPublicExponent().toString();

            keys[1][0] = privateKeySpec.getModulus().toString();
            keys[1][1] = privateKeySpec.getPrivateExponent().toString();

            return keys;
        } catch (Exception e) {
            log.error("generateRSAKeyString is error", e);
        }
        return null;
    }

    /**
     * 构造一个RSA公钥
     *
     * @param modulus  公钥模量
     * @param exponent 公钥指数
     */
    public static RSAPublicKey genarateRSAPublicKey(String modulus, String exponent) {
        try {
            RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
            return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
        } catch (Exception e) {
            log.error("genarateRSAPublicKey is error", e);
        }
        return null;
    }

    /**
     * 构造一个RSA私钥对象
     *
     * @param modulus  私钥模量
     * @param exponent 私钥指数
     */
    public static RSAPrivateKey generateRSAPrivateKey(String modulus, String exponent) {
        try {
            RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(exponent));
            return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);
        } catch (Exception e) {
            log.error("generateRSAPrivateKey is error", e);
        }
        return null;
    }

    /**
     * 根据路径获取公钥或私钥
     *
     * @param path 密钥路径
     */
    public static Key genetateRSAKey(String path) {
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
            return (Key) ois.readObject();
        } catch (Exception e) {
            log.error("genetateRSAKey is error", e);
        }
        return null;
    }

    /**
     * 字符串加密（AES 128位对称加密）
     *
     * @param data 加密字符串
     * @param key  私钥
     */
    public static String encryptByAES(String data, String key) {
        try {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            byte[] hash = Base64.getDecoder().decode(key.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(hash, 0, hash.length, "AES"));
            return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
        } catch (Exception e) {
            log.error("encryptByAES is error", e);
        }
        return null;
    }

    /**
     * 字符串解密（AES 128位对称解密）
     *
     * @param data 解密字符串
     * @param key  私钥
     */
    public static String decryptByAES(String data, String key) {
        try {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            byte[] hash = Base64.getDecoder().decode(key.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hash, 0, hash.length, "AES"));
            return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
        } catch (Exception e) {
            log.error("decryptByAES is error", e);
        }
        return null;
    }

    /**
     * 生成128位AES私钥
     */
    public static String generateAESKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128, new SecureRandom());
            return Base64.getEncoder().encodeToString(keyGenerator.generateKey().getEncoded());
        } catch (Exception e) {
            log.error("generateAESKey is error", e);
        }
        return null;
    }

    /**
     * MD5加密
     *
     * @param data 需要加密的数据
     */
    public static String MD5(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(data.getBytes());
            return new BigInteger(1, md.digest()).toString(16);
        } catch (Exception e) {
            log.error("MD5 is error", e);
        }
        return null;
    }
}